/*******************************************************************************
 * Includes
 ******************************************************************************/
#include "Wed_phy.h"

/*******************************************************************************
 * Static
 ******************************************************************************/
static struct Wed_list_head __g_phy_list;
static struct Wed_mutex     __g_phy_list_lock;

/*******************************************************************************
 * Code
 ******************************************************************************/
/**
 * \brief PHY 设备链表初始化
 *
 * @return 成功返回 0
 */
int Wed_phy_list_init(void){
    Wed_list_head_init(&__g_phy_list);

    return Wed_mutex_init(&__g_phy_list_lock);
}

/**
 * \brief 设备添加函数
 */
static int __dev_add(struct Wed_phy *p_phy){
    int ret = 0;

    ret = Wed_mutex_lock(&__g_phy_list_lock, 5000);
    if (ret != 0) {
        return ret;
    }

    Wed_list_node_add_tail(&p_phy->node, &__g_phy_list);

    Wed_mutex_unlock(&__g_phy_list_lock);

    return ret;
}

/* \brief 设备操作函数集 */
static struct Wed_dev_opts __g_phy_opts = {

};

/**
 * \brief PHY 设备注册函数
 *
 * @param p_phy_name   PHY 设备名字
 * @param p_opts       MII 操作函数集
 * @param p_opts_arg   操作函数参数
 * @param p_reset_name 复位引脚名称
 *
 * @return 成功返回 0
 */
int Wed_phy_register(char                *p_phy_name,
                     struct Wed_mii_opts *p_opts,
                     void                *p_opts_arg,
                     char                *p_reset_name){
    int             ret;
    struct Wed_phy *p_phy = NULL;

    if ((p_phy_name == NULL) ||
            (strlen(p_phy_name) >= WED_DEV_NAME_LEN) ||
            (p_opts == NULL)) {
        return -WED_EINVAL;
    }

    /* 检查设备是否已存在 */
    if (Wed_dev_find_by_name(p_phy_name)) {
        return -WED_EEXIST;
    }

#if WED_MEM_DYMANICS
    p_phy = Wed_calloc(1, sizeof(struct Wed_phy));
    if (p_phy == NULL) {
        return -WED_ENOMEM;
    }

    p_phy->p_opts       = p_opts;
    p_phy->p_opts_arg   = p_opts_arg;
    p_phy->p_reset_name = p_reset_name;
    //p_uart_dev->flag       = flag;

    ret = Wed_mutex_init(&p_phy->lock);
    if (ret != 0) {
        goto __failed;
    }

    ret = Wed_dev_init_add(&p_phy->dev,
                            p_phy_name,
                            WED_DEV_TYPE_PHY,
                         &__g_phy_opts,
                            p_phy);
    if (ret == 0) {
        __dev_add(p_phy);

        return 0;
    }

__failed:
    Wed_free(p_phy);

    return ret;
#else

#endif
}

/**
 * \brief 根据名字获取 PHY 设备结构体
 *
 * @param p_gpio_name PHY 名字
 *
 * @return 找到返回 PHY 设备结构体，没找到返回 NULL
 */
struct Wed_phy *Wed_phy_find_by_name(char *p_phy_name){
    int                   ret;
    struct Wed_list_node *p_node = NULL;
    struct Wed_phy       *p_dev  = NULL;

    ret = Wed_mutex_lock(&__g_phy_list_lock, 5000);
    if (ret != 0) {
        return NULL;
    }

    Wed_list_for_each_node(p_node, &__g_phy_list){
        p_dev = WED_CONTAINER_OF(p_node, struct Wed_phy, node);
        if (strcmp(p_dev->dev.name, p_phy_name) == 0) {
            Wed_mutex_unlock(&__g_phy_list_lock);

            return p_dev;
        }
    }

    Wed_mutex_unlock(&__g_phy_list_lock);

    return NULL;
}

/**
 * \brief PHY 设备复位
 *
 * @param p_phy    PHY 设备名字
 * @param phy_addr PHY 设备地址
 * @param time_out 超时时间（毫秒）
 *
 * @return 成功返回 0
 */
int Wed_phy_reset(struct Wed_phy *p_phy, int phy_addr, uint32_t time_out){
    int              ret;
    uint16_t         data   = 0;
    struct Wed_gpio *p_gpio = NULL;

    if (p_phy == NULL) {
        return -WED_EINVAL;
    }

    if (p_phy->p_reset_name) {
        p_gpio = Wed_gpio_find_by_name(p_phy->p_reset_name);
        if (p_gpio == NULL) {
            return -WED_ENODEV;
        }
        Wed_gpio_sta_set(p_gpio, 0);
        clk_cpu_delay_ms(10);
        Wed_gpio_sta_set(p_gpio, 1);
        clk_cpu_delay_ms(2);
    } else {
        ret = p_phy->p_opts->p_fn_mdio_read(p_phy->p_opts_arg,
                                            phy_addr,
                                            GEN_PHY_CTRL_REG,
                                           &data);
        if (ret != 0) {
            return ret;
        }

        data |= GEN_PHY_CR_RESET;

        ret = p_phy->p_opts->p_fn_mdio_write(p_phy->p_opts_arg,
                                             phy_addr,
                                             GEN_PHY_CTRL_REG,
                                             data);
        do {
            Wed_mdelay(1);

            ret = p_phy->p_opts->p_fn_mdio_read(p_phy->p_opts_arg,
                                                phy_addr,
                                                GEN_PHY_CTRL_REG,
                                               &data);
            if (ret != 0) {
                return ret;
            }
            if ((--time_out) == 0) {
                return -WED_ETIME;
            }
        } while(data & GEN_PHY_CR_RESET);
    }

    return 0;
}

/**
 * \brief PHY 设备下电
 *
 * @param p_phy    PHY 设备名字
 * @param phy_addr PHY 设备地址
 *
 * @return 成功返回 0
 */
int Wed_phy_down(struct Wed_phy *p_phy, int phy_addr){
    int      ret;
    uint16_t data = 0;

    if (p_phy == NULL) {
        return -WED_EINVAL;
    }

    ret = p_phy->p_opts->p_fn_mdio_read(p_phy->p_opts_arg, phy_addr, GEN_PHY_CTRL_REG, &data);
    if (ret != 0) {
        return ret;
    }

    /* PHY 设备下电 */
    data |= GEN_PHY_CR_PWR_DOWN;
    return p_phy->p_opts->p_fn_mdio_write(p_phy->p_opts_arg, phy_addr, GEN_PHY_CTRL_REG, data);
}

/**
 * \brief PHY 设备上电
 *
 * @param p_phy    PHY 设备名字
 * @param phy_addr PHY 设备地址
 *
 * @return 成功返回 0
 */
int Wed_phy_up(struct Wed_phy *p_phy, int phy_addr){
    int      ret;
    uint16_t data = 0;

    ret = p_phy->p_opts->p_fn_mdio_read(p_phy->p_opts_arg, phy_addr, GEN_PHY_CTRL_REG, &data);
    if (ret != 0) {
        return ret;
    }

    /* PHY 设备上电 */
    data &= ~GEN_PHY_CR_PWR_DOWN;
    return p_phy->p_opts->p_fn_mdio_write(p_phy->p_opts_arg, phy_addr, GEN_PHY_CTRL_REG, data);
}

static int __set_all_ability(struct Wed_phy *p_phy, int phy_addr, uint8_t choice) {
    int      ret;
    uint16_t data;

    ret = p_phy->p_opts->p_fn_mdio_read(p_phy->p_opts_arg, phy_addr, GEN_PHY_ANAR_REG, &data);
    if (ret != 0) {
        return ret;
    }

    if (choice) {
        data |= DISABLE_100M_10M_ABILITY;
    } else {
        data &= ~DISABLE_100M_10M_ABILITY;
    }
    return p_phy->p_opts->p_fn_mdio_write(p_phy->p_opts_arg, phy_addr, GEN_PHY_ANAR_REG, data);
}

/**
 * \brief PHY 设备工作模式设置
 *
 * @param p_phy    PHY 设备名字
 * @param phy_addr PHY 设备地址
 * @param p_cfg    配置结构体
 *
 * @return 成功返回 0
 */
int Wed_phy_work_model_set(struct Wed_phy     *p_phy,
                           int                 phy_addr,
                           struct Wed_phy_cfg *p_cfg){
    int      ret;
    uint16_t data = 0;

    ret = p_phy->p_opts->p_fn_mdio_read(p_phy->p_opts_arg, phy_addr, GEN_PHY_CTRL_REG, &data);
    if (ret != 0) {
        return ret;
    }

    /* 关闭自动协商、强制速率默认为10M、关闭MII回环、关闭全双工 */
    data &= ~(GEN_PHY_CR_AUTO_NEGO |
              GEN_PHY_CR_SPEEDMSB  |
              GEN_PHY_CR_SPEEDLSB  |
              GEN_PHY_CR_LOOPBACK  |
              GEN_PHY_CR_FULL_DUPLEX);

    /* 关闭所有能力宣传 */
    ret = __set_all_ability(p_phy, phy_addr, 0);
    if (ret != 0) {
        return ret;
    }

    /* 开启自动协商并重新自动协商 保证能力展示能更新至对端 */
    data |= GEN_PHY_CR_AUTO_NEGO | GEN_PHY_CR_AUTO_NEGO_RESTART;

    ret = __set_all_ability(p_phy, phy_addr, 1);
    if (ret != 0) {
        return ret;
    }

    return p_phy->p_opts->p_fn_mdio_write(p_phy->p_opts_arg, phy_addr, GEN_PHY_CTRL_REG, data);
}
